package com.myjpa.common.criteria;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.myjpa.common.date.DateUtils;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import java.math.BigDecimal;
import java.util.*;

/**
 * @author: yaoll
 * @date: 2020-09-04
 * @verison: 1.0
 *
 * <pre>
 * {
 *     "page": 1,
 *     "size": 10,
 *     "onlyCount": false,
 *     "criteria": {
 *         "name": "zhangsan",
 *         "name": [ "zhangsan", "lisi" ],
 *         "age": {
 *             "$gt": 18,
 *             "$lt": 35
 *         },
 *         "gender": {
 *             "$null": false
 *         }
 *     },
 *     "orders": [
 *     		{
 *     		 	"column": "name",
 *     		 	"asc": false
 *     		}
 *     ],
 *     "withColumns": [
 *     		"name", "age"
 *     ]
 * }
 * </pre>
 */
public class JacksonCriteria {

	@Getter
	@Setter
	private Integer page;

	@Getter
	@Setter
	private Integer size;

	/**
	 * 是否只统计数量
	 */
	@Getter
	@Setter
	private boolean onlyCount = false;

	@Getter
	@Setter
	private ObjectNode criteria;

	@Getter
	@Setter
	private List<Order> orders;

	@Getter
	@Setter
	private Set<String> withColumns;

	public boolean withColumn(String column){
		return withColumns != null && withColumns.contains(column);
	}

	public JacksonCriteria addWithColumn(String column){
		if(withColumns == null){
			withColumns = new HashSet<>();
		}
		withColumns.add(column);
		return this;
	}

	public JacksonCriteria addOrder(String column) {
		return addOrder(column, true);
	}

	public JacksonCriteria addOrder(String column, boolean asc) {
		if (orders == null) {
			orders = new LinkedList<>();
		}
		orders.add(new Order(column, asc));
		return this;
	}

	public JacksonCriteria setLimit(int page, int size) {
		this.page = page;
		this.size = size;
		return this;
	}

	private JacksonCriteria() {
		super();
		this.criteria = JsonNodeFactory.instance.objectNode();
	}

	public static JacksonCriteria newInstance() {
		return new JacksonCriteria();
	}

	public String getString(String nodeName) {
		return getString(nodeName, null);
	}

	public String getString(String nodeName, String defaultValue) {
		if (criteria.has(nodeName)) {
			JsonNode node = criteria.get(nodeName);
			return node.asText();
		}
		return defaultValue;
	}

	public Integer getInteger(String nodeName) {
		return getInteger(nodeName, null);
	}

	public Integer getInteger(String nodeName, Integer defaultValue) {
		if (criteria.has(nodeName)) {
			JsonNode jsonNode = criteria.get(nodeName);
			return Integer.valueOf(jsonNode.asText());
		}
		return defaultValue;
	}

	public Double getDouble(String nodeName) {
		return getDouble(nodeName, null);
	}

	public Double getDouble(String nodeName, Double defaultValue) {
		if (criteria.has(nodeName)) {
			JsonNode jsonNode = criteria.get(nodeName);
			return Double.valueOf(jsonNode.asText());
		}
		return defaultValue;
	}

	public Boolean getBoolean(String nodeName) {
		return getBoolean(nodeName, null);
	}

	public Boolean getBoolean(String nodeName, Boolean defaultValue) {
		if (criteria.has(nodeName)) {
			JsonNode jsonNode = criteria.get(nodeName);
			return Boolean.valueOf(jsonNode.asText());
		}
		return defaultValue;
	}

	/**
	 * 添加查询key,重复添加时后添加的会覆盖前面添加的
	 *
	 * @param key
	 * @param value
	 * @return
	 */
	public void add(String key, String value) {
		criteria.put(key, value);
	}

	public void add(String key, byte value) {
		criteria.put(key, value);
	}

	public void add(String key, short value) {
		criteria.put(key, value);
	}

	public void add(String key, int value) {
		criteria.put(key, value);
	}

	public void add(String key, long value) {
		criteria.put(key, value);
	}

	public void add(String key, float value) {
		criteria.put(key, value);
	}

	public void add(String key, double value) {
		criteria.put(key, value);
	}

	public void add(String key, BigDecimal value) {
		criteria.put(key, value);
	}

	public void add(String key, boolean value) {
		criteria.put(key, value);
	}

	public void add(String key, byte[] value) {
		criteria.put(key, value);
	}

	public <T> void add(String string, List<T> value) {
		if (value != null) {
			ArrayNode jsonNodes = criteria.putArray(string);
			toArrayNode(jsonNodes, value);
		}
	}

	public void add(String string, Enum value) {
		if (value != null) {
			criteria.put(string, value.name());
		}
	}

	@JsonIgnore
	private ObjectNode current;

	/**
	 * 添加查询key,重复添加时后添加的会覆盖前面添加的
	 *
	 * @param key
	 * @return
	 */
	public JacksonCriteria add(String key) {
		current = criteria.putObject(key);
		return this;
	}

	public JacksonCriteria isNull(boolean value) {
		current.put("$null", value);
		return this;
	}

	public JacksonCriteria gt(byte value) {
		current.put("$gt", value);
		return this;
	}

	public JacksonCriteria gt(short value) {
		current.put("$gt", value);
		return this;
	}

	public JacksonCriteria gt(int value) {
		current.put("$gt", value);
		return this;
	}

	public JacksonCriteria gt(long value) {
		current.put("$gt", value);
		return this;
	}

	public JacksonCriteria gt(float value) {
		current.put("$gt", value);
		return this;
	}

	public JacksonCriteria gt(double value) {
		current.put("$gt", value);
		return this;
	}

	public JacksonCriteria gt(String value) {
		current.put("$gt", value);
		return this;
	}

	public JacksonCriteria gt(Date value) {
		current.put("$gt", DateUtils.format(value));
		return this;
	}

	public JacksonCriteria gte(byte value) {
		current.put("$gte", value);
		return this;
	}

	public JacksonCriteria gte(short value) {
		current.put("$gte", value);
		return this;
	}

	public JacksonCriteria gte(int value) {
		current.put("$gte", value);
		return this;
	}

	public JacksonCriteria gte(long value) {
		current.put("$gte", value);
		return this;
	}

	public JacksonCriteria gte(float value) {
		current.put("$gte", value);
		return this;
	}

	public JacksonCriteria gte(double value) {
		current.put("$gte", value);
		return this;
	}

	public JacksonCriteria gte(String value) {
		current.put("$gte", value);
		return this;
	}

	public JacksonCriteria gte(Date value) {
		current.put("$gte", DateUtils.format(value));
		return this;
	}

	public JacksonCriteria lt(byte value) {
		current.put("$lt", value);
		return this;
	}

	public JacksonCriteria lt(short value) {
		current.put("$lt", value);
		return this;
	}

	public JacksonCriteria lt(int value) {
		current.put("$lt", value);
		return this;
	}

	public JacksonCriteria lt(long value) {
		current.put("$lt", value);
		return this;
	}

	public JacksonCriteria lt(float value) {
		current.put("$lt", value);
		return this;
	}

	public JacksonCriteria lt(double value) {
		current.put("$lt", value);
		return this;
	}

	public JacksonCriteria lt(String value) {
		current.put("$lt", value);
		return this;
	}

	public JacksonCriteria lt(Date value) {
		current.put("$lt", DateUtils.format(value));
		return this;
	}

	public JacksonCriteria lte(byte value) {
		current.put("$lte", value);
		return this;
	}

	public JacksonCriteria lte(short value) {
		current.put("$lte", value);
		return this;
	}

	public JacksonCriteria lte(int value) {
		current.put("$lte", value);
		return this;
	}

	public JacksonCriteria lte(long value) {
		current.put("$lte", value);
		return this;
	}

	public JacksonCriteria lte(float value) {
		current.put("$lte", value);
		return this;
	}

	public JacksonCriteria lte(double value) {
		current.put("$lte", value);
		return this;
	}

	public JacksonCriteria lte(String value) {
		current.put("$lte", value);
		return this;
	}

	public JacksonCriteria lte(Date value) {
		current.put("$lte", DateUtils.format(value));
		return this;
	}

	public JacksonCriteria ne(byte value) {
		current.put("$ne", value);
		return this;
	}

	public JacksonCriteria ne(short value) {
		current.put("$ne", value);
		return this;
	}

	public JacksonCriteria ne(int value) {
		current.put("$ne", value);
		return this;
	}

	public JacksonCriteria ne(long value) {
		current.put("$ne", value);
		return this;
	}

	public JacksonCriteria ne(float value) {
		current.put("$ne", value);
		return this;
	}

	public JacksonCriteria ne(double value) {
		current.put("$ne", value);
		return this;
	}

	public JacksonCriteria ne(String value) {
		current.put("$ne", value);
		return this;
	}

	public JacksonCriteria ne(Date value) {
		current.put("$ne", DateUtils.format(value));
		return this;
	}

	public JacksonCriteria like(String value) {
		current.put("$like", value);
		return this;
	}

	public JacksonCriteria notLike(String value) {
		current.put("$notLike", value);
		return this;
	}

	public <T> JacksonCriteria in(List<T> value) {
		if (value != null) {
			ArrayNode inNode = current.putArray("$in");
			toArrayNode(inNode, value);
		}
		return this;
	}

	private <T> void toArrayNode(ArrayNode arrayNode, List<T> value) {
		value.forEach(o -> {
			if (o instanceof String) {
				arrayNode.add((String) o);
			} else if (o instanceof Byte) {
				arrayNode.add((Byte) o);
			} else if (o instanceof Short) {
				arrayNode.add((Short) o);
			} else if (o instanceof Integer) {
				arrayNode.add((Integer) o);
			} else if (o instanceof Long) {
				arrayNode.add((Long) o);
			} else if (o instanceof Float) {
				arrayNode.add((Float) o);
			} else if (o instanceof Double) {
				arrayNode.add((Double) o);
			} else if (o instanceof Boolean) {
				arrayNode.add((Boolean) o);
			} else if (o instanceof byte[]) {
				arrayNode.add((byte[]) o);
			} else if (o instanceof Enum) {
				arrayNode.add(((Enum) o).name());
			}
		});
	}

	@Data
	public static class Order {
		private String column;
		private boolean asc = true;

		public Order() {
		}

		public Order(String column) {
			this.column = column;
		}

		public Order(String column, boolean asc) {
			this.column = column;
			this.asc = asc;
		}
	}
}
